﻿using System;
using System.Data;
using Data.Core.UoW;
using Infrastructure.CrossCutting.Checks;
using NHibernate;
using ITransaction = Data.Core.UoW.ITransaction;

namespace Infrastructure.Data.NH.Core.UoW
{
    public class NHUnitOfWork : IUnitOfWork
    {
        private bool disposed;
        private ITransaction transaction;
        private ISession session;

        #region ctor

        public NHUnitOfWork(ISession session)
        {
            Check.Against<ArgumentNullException>(session == null, "Cannot create a NHUnitOfWork that uses a null reference ISession instance.");
            this.session = session;
        }
        #endregion

        /// <summary>
        /// Gets the <see cref="ISession"/> that the <see cref="NHUnitOfWork"/> wraps.
        /// </summary>
        public ISession Session
        {
            get { return session; }
        } 

        #region Implementation of IDisposable

        /// <summary>
        /// Führt anwendungsspezifische Aufgaben aus, die mit dem Freigeben, Zurückgeben oder Zurücksetzen von nicht verwalteten Ressourcen zusammenhängen.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
		/// Disposes off managed resources used by the NHUnitOfWork instance.
		/// </summary>
		/// <param name="disposing"></param>
		private void Dispose(bool disposing)
        {
            if (!disposing) return;
            if (disposed) return;

            if(transaction != null)
            {
                transaction.Dispose();
                transaction = null;
            }

            if(session != null)
            {
                session.Dispose();
                session = null;
            }

            disposed = true;
        }
        #endregion

        #region Implementation of IUnitOfWork

        /// <summary>
        /// Gets a boolean value indicating whether the current unit of work is running under
        /// a transaction.
        /// </summary>
        public bool IsInTransaction { get { return transaction != null; } }

        /// <summary>
        /// Gets a value indicating whether this instance's transaction is rolledback.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this transaction is rolledback; otherwise, <c>false</c>.
        /// </value>
        public bool IsRolledback { get; private set; }

        /// <summary>
        /// Gets a value indicating whether this instance's transaction is committed.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this transaction is committed; otherwise, <c>false</c>.
        /// </value>
        public bool IsCommitted { get; private set; }

        /// <summary>
        /// Instructs the <see cref="IUnitOfWork"/> instance to begin a new transaction.
        /// </summary>
        /// <returns></returns>
        public ITransaction BeginTransaction()
        {
            return BeginTransaction(IsolationLevel.ReadCommitted);
        }

        /// <summary>
        /// Instructs the <see cref="IUnitOfWork"/> instance to begin a new transaction
        /// with the specified isolation level.
        /// </summary>
        /// <param name="isolationLevel">One of the values of <see cref="IsolationLevel"/>
        /// that specifies the isolation level of the transaction.</param>
        /// <returns></returns>
        public ITransaction BeginTransaction(IsolationLevel isolationLevel)
        {
            Check.Against<InvalidOperationException>(transaction != null, "Can not begin a new transaction while an existing transaction is still running. Please commit or rollback the existing transaction before starting a new one.");

            IsRolledback = false;
            IsCommitted = false;

            transaction = new NHTransaction(session.BeginTransaction(isolationLevel));
            transaction.TransactionCommitted += TransactionCommitted;
            transaction.TransactionRolledback += TransactionRolledback;

            return transaction;
        }

        void TransactionRolledback(object sender, EventArgs e)
        {
            Check.IsEqual<InvalidOperationException>(sender, transaction,
                        "Expected the sender of TransactionRolledback event to be the transaction that was created by the NHUnitOfWork instance.");
            IsRolledback = true;
            ReleaseCurrentTransaction();
        }

        void TransactionCommitted(object sender, EventArgs e)
        {
            Check.IsEqual<InvalidOperationException>(sender, transaction,
                   "Expected the sender of TransactionComitted event to be the transaction that was created by the NHUnitOfWork instance.");
            IsCommitted = true;
            ReleaseCurrentTransaction();

        }

        /// <summary>
        /// Flushes the changes made in the unit of work to the data store.
        /// </summary>
        public void Flush()
        {
            session.Flush();
        }

        /// <summary>
        /// Flushes the changes made in the unit of work to the data store
        /// within a transaction.
        /// </summary>
        public void TransactionalFlush()
        {
            TransactionalFlush(IsolationLevel.ReadCommitted);
        }

        /// <summary>
        /// Flushes the changes made in the unit of work to the data store
        /// within a transaction with the specified isolation level.
        /// </summary>
        /// <param name="isolationLevel"></param>
        public void TransactionalFlush(IsolationLevel isolationLevel)
        {
            // Start a transaction if one isn't already running.
            if (!IsInTransaction)
                BeginTransaction(isolationLevel);
            try
            {
                session.Flush();
                transaction.Commit();
            }
            catch
            {
                transaction.Rollback();
                throw;
            }
        }

        #endregion

        /// <summary>
        /// Releases the current transaction in the <see cref="NHUnitOfWork"/> instance.
        /// </summary>
        private void ReleaseCurrentTransaction()
        {
            if (transaction != null)
            {
                transaction.TransactionCommitted -= TransactionCommitted;
                transaction.TransactionRolledback -= TransactionRolledback;
                transaction.Dispose();
            }
            transaction = null;
        }
    }
}
